package com.platform.common.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
 * <pre>
 * 功能：数据加密解密工具类
 * 支持HMAC-SHA1消息签名 及 AES/Blowfish对称加密的工具类
 * 支持Hex与Base64两种编码方式
 * 支持MD5 SHA 不可逆加密
 * </pre>
 * 
 * @author wei.gao
 */
public class DigestUtils extends org.apache.commons.codec.digest.DigestUtils {
	
	public static final String SHA1 = "SHA-1";
	public static final String MD5 = "MD5";
	
	private static final String AES = "AES";
	private static final String AES_CBC = "AES/CBC/PKCS5Padding";
	private static final String HMACSHA1 = "HmacSHA1";
	private static final int DEFAULT_HMACSHA1_KEYSIZE = 160; //RFC2401
	private static final int DEFAULT_AES_KEYSIZE = 128;
	private static final int DEFAULT_IVSIZE = 16;
	
	private static SecureRandom random = new SecureRandom();
	
	/**
	 * MD5
	 * 
	 * @param input
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws UnsupportedEncodingException
	 */
	public static String MD5Hashing(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		String output = "";
		MessageDigest md = MessageDigest.getInstance("MD5");
		byte[] original = input.getBytes("UTF-8");
		byte[] bytes = md.digest(original);
		for (int i = 0; i < bytes.length; i++) {
			output += Integer.toHexString((bytes[i] & 0xff) + 0x100).substring(1);
		}
		return output;
	}
	
	/**
	 * <pre>
	 * 功能：SHA加密
	 * </pre>
	 * 
	 * @param input
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws UnsupportedEncodingException
	 */
	public static String SHAHashing(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		MessageDigest md = MessageDigest.getInstance("SHA");
		byte[] original = input.getBytes("UTF-8");
		byte[] bytes = md.digest(original);
		return ConvertUtils.convertEncodeBase64(bytes);
	}
	
	/**
	 * <pre>
	 * 功能：加密[Blowfish对称加密解密] 密钥是配置中的site.secret
	 * </pre>
	 * 
	 * @param key
	 * @param paramString
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws UnsupportedEncodingException
	 */
	public static String encrypt(String key, String paramString) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
		if (ValidateUtils.isNull(paramString)) {
			return paramString;
		}
		Cipher localCipher = Cipher.getInstance("Blowfish");
		localCipher.init(1, new SecretKeySpec(key.getBytes(), "Blowfish"));
		byte[] arrayOfByte = localCipher.doFinal(paramString.getBytes("UTF-8"));
		return ConvertUtils.convertEncodeBase64(arrayOfByte);
	}
	
	/**
	 * <pre>
	 * 功能：解密[Blowfish对称加密解密] 密钥是配置中的site.secret
	 * </pre>
	 * 
	 * @param key
	 * @param paramString
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws UnsupportedEncodingException
	 */
	public static String decrypt(String key, String paramString) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
		if (ValidateUtils.isNull(paramString)) {
			return paramString;
		}
		Cipher localCipher = Cipher.getInstance("Blowfish");
		localCipher.init(2, new SecretKeySpec(key.getBytes(), "Blowfish"));
		byte[] arrayOfByte = localCipher.doFinal(ConvertUtils.convertDecodeBase64(paramString));
		return new String(arrayOfByte, "UTF-8");
	}
	
	/**
	 * <pre>
	 * 功能：使用HMAC-SHA1进行消息签名, 返回字节数组,长度为20字节.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	public static byte[] hmacSha1(byte[] input, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
		SecretKey secretKey = new SecretKeySpec(key, HMACSHA1);
		Mac mac = Mac.getInstance(HMACSHA1);
		mac.init(secretKey);
		return mac.doFinal(input);
	}
	
	/**
	 * <pre>
	 * 功能：校验HMAC-SHA1签名是否正确.
	 * </pre>
	 * 
	 * @param expected
	 * @param input
	 * @param key
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	public static boolean isMacValid(byte[] expected, byte[] input, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException {
		byte[] actual = hmacSha1(input, key);
		return Arrays.equals(expected, actual);
	}
	
	/**
	 * <pre>
	 * 功能：生成HMAC-SHA1密钥,返回字节数组,长度为160位(20字节). HMAC-SHA1算法对密钥无特殊要求,
	 * RFC2401建议最少长度为160位(20字节).
	 * </pre>
	 * 
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public static byte[] generateHmacSha1Key() throws NoSuchAlgorithmException {
		KeyGenerator keyGenerator = KeyGenerator.getInstance(HMACSHA1);
		keyGenerator.init(DEFAULT_HMACSHA1_KEYSIZE);
		SecretKey secretKey = keyGenerator.generateKey();
		return secretKey.getEncoded();
	}
	
	/**
	 * <pre>
	 * 功能：使用AES加密原始字符串.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @return
	 * @throws BadPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws NoSuchPaddingException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	public static byte[] aesEncrypt(byte[] input, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		return aes(input, key, Cipher.ENCRYPT_MODE);
	}
	
	/**
	 * <pre>
	 * 功能：使用AES加密原始字符串.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @param iv
	 * @return
	 * @throws InvalidKeyException
	 * @throws InvalidAlgorithmParameterException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 */
	public static byte[] aesEncrypt(byte[] input, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
		return aes(input, key, iv, Cipher.ENCRYPT_MODE);
	}
	
	/**
	 * <pre>
	 * 功能：使用AES解密字符串, 返回原始字符串.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static String aesDecrypt(byte[] input, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		byte[] decryptResult = aes(input, key, Cipher.DECRYPT_MODE);
		return new String(decryptResult);
	}
	
	/**
	 * <pre>
	 * 功能：使用AES解密字符串, 返回原始字符串.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @param iv
	 * @return
	 * @throws NoSuchPaddingException
	 * @throws NoSuchAlgorithmException
	 * @throws BadPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws InvalidAlgorithmParameterException
	 * @throws InvalidKeyException
	 */
	public static String aesDecrypt(byte[] input, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
		byte[] decryptResult = aes(input, key, iv, Cipher.DECRYPT_MODE);
		return new String(decryptResult);
	}
	
	/**
	 * <pre>
	 * 功能：使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @param mode
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	private static byte[] aes(byte[] input, byte[] key, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		SecretKey secretKey = new SecretKeySpec(key, AES);
		Cipher cipher = Cipher.getInstance(AES);
		cipher.init(mode, secretKey);
		return cipher.doFinal(input);
	}
	
	/**
	 * <pre>
	 * 功能：使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
	 * </pre>
	 * 
	 * @param input
	 * @param key
	 * @param iv
	 * @param mode
	 * @return
	 * @throws InvalidAlgorithmParameterException
	 * @throws InvalidKeyException
	 * @throws BadPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws NoSuchPaddingException
	 * @throws NoSuchAlgorithmException
	 */
	private static byte[] aes(byte[] input, byte[] key, byte[] iv, int mode) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
		SecretKey secretKey = new SecretKeySpec(key, AES);
		IvParameterSpec ivSpec = new IvParameterSpec(iv);
		Cipher cipher = Cipher.getInstance(AES_CBC);
		cipher.init(mode, secretKey, ivSpec);
		return cipher.doFinal(input);
	}
	
	/**
	 * <pre>
	 * 功能：生成AES密钥,返回字节数组, 默认长度为128位(16字节).
	 * </pre>
	 * 
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public static byte[] generateAesKey() throws NoSuchAlgorithmException {
		return generateAesKey(DEFAULT_AES_KEYSIZE);
	}
	
	/**
	 * <pre>
	 * 功能：生成AES密钥,可选长度为128,192,256位.
	 * </pre>
	 * 
	 * @param keysize
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public static byte[] generateAesKey(int keysize) throws NoSuchAlgorithmException {
		KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
		keyGenerator.init(keysize);
		SecretKey secretKey = keyGenerator.generateKey();
		return secretKey.getEncoded();
	}
	
	/**
	 * <pre>
	 * 功能：生成随机向量,默认大小为cipher.getBlockSize(), 16字节.
	 * </pre>
	 * 
	 * @return
	 */
	public static byte[] generateIV() {
		byte[] bytes = new byte[DEFAULT_IVSIZE];
		random.nextBytes(bytes);
		return bytes;
	}
	
	/**
	 * <pre>
	 * 功能：对输入字符串进行sha1散列.
	 * </pre>
	 * 
	 * @param input
	 * @return
	 */
	public static byte[] sha1(byte[] input) {
		return digest(input, SHA1, null, 1);
	}
	
	/**
	 * <pre>
	 * 功能：SHA加盐算法
	 * </pre>
	 * 
	 * @param input
	 * @param salt
	 * @return
	 */
	public static byte[] sha1(byte[] input, byte[] salt) {
		return digest(input, SHA1, salt, 1);
	}
	
	/**
	 * <pre>
	 * 功能：SHA加盐多次循环算法
	 * </pre>
	 * 
	 * @param input
	 * @param salt
	 * @param iterations
	 * @return
	 */
	public static byte[] sha1(byte[] input, byte[] salt, int iterations) {
		return digest(input, SHA1, salt, iterations);
	}
	
	/**
	 * <pre>
	 * 功能：对字符串进行散列, 支持md5与sha1算法.
	 * </pre>
	 * 
	 * @param input
	 * @param algorithm
	 * @param salt
	 * @param iterations
	 * @return
	 */
	private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) {
		try {
			MessageDigest digest = MessageDigest.getInstance(algorithm);
			
			if (salt != null) {
				digest.update(salt);
			}
			
			byte[] result = digest.digest(input);
			
			for (int i = 1; i < iterations; i++) {
				digest.reset();
				result = digest.digest(result);
			}
			return result;
		} catch (GeneralSecurityException e) {
			throw ExcptionUtils.unchecked(e);
		}
	}
	
	/**
	 * <pre>
	 * 功能：生成随机的Byte[]作为salt.
	 * </pre>
	 * 
	 * @param numBytes
	 * @return
	 */
	public static byte[] generateSalt(int numBytes) {
		byte[] bytes = new byte[numBytes];
		random.nextBytes(bytes);
		return bytes;
	}
	
	/**
	 * <pre>
	 * 功能：对文件进行消息摘要签名
	 * </pre>
	 * 
	 * @param input
	 * @param algorithm
	 * @return
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 */
	public static byte[] digest(InputStream input, String algorithm) throws IOException, NoSuchAlgorithmException {
		MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
		int bufferLength = 8 * 1024;
		byte[] buffer = new byte[bufferLength];
		int read = input.read(buffer, 0, bufferLength);
		
		while (read > -1) {
			messageDigest.update(buffer, 0, read);
			read = input.read(buffer, 0, bufferLength);
		}
		
		return messageDigest.digest();
	}
	
	/**
	 * <pre>
	 * 功能：SHA加密
	 * </pre>
	 * 
	 * @param inputStr
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws UnsupportedEncodingException
	 */
	public static String encryptSHA256(String inputStr) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		MessageDigest md = MessageDigest.getInstance("SHA-256");
		byte digest[] = md.digest(inputStr.getBytes("UTF-8"));
		return new String(Base64.encodeBase64(digest));
	}
}
